package com.yesep.learn.jwt.config;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.yesep.learn.jwt.util.SpringUtils;

import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "security-jwt")
public class JWTTokenConfig implements Serializable {

	private static final long serialVersionUID = -3301605591108950415L;
	private static final Logger logger = LoggerFactory.getLogger(JWTTokenConfig.class);

	public static final String CLAIM_KEY_USERNAME = "sub";
	public static final String CLAIM_KEY_CREATED = "created";
	public static final String CLAIM_KEY_AUTH = "auth";
	private String header;

	private String secret;

	private Long expiration;
	private String loginPage;
	private String loginProcessingUrl;
	private String logoutUrl;
	private List<String> whitelist;

	public static String TOKEN_PREFIX = "Bearer ";

	public static JWTTokenConfig me() {
		return SpringUtils.getBean(JWTTokenConfig.class);
	}

	public String getUsernameFromToken(String token) throws ClaimJwtException {
		String username = null;
		Claims claims = getClaimsFromToken(token);
		if (null != claims)
			username = claims.getSubject();
		return username;
	}

	public Date getCreatedDateFromToken(String token) throws ClaimJwtException {
		Date created = null;
		Claims claims = getClaimsFromToken(token);
		if (null != claims)
			created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
		return created;
	}

	public Date getExpirationDateFromToken(String token) throws ClaimJwtException {
		Date expiration = null;
		Claims claims = getClaimsFromToken(token);
		if (null != claims)
			expiration = claims.getExpiration();
		return expiration;
	}

	public Claims getClaimsFromToken(String token) throws ClaimJwtException {
		try {
			Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
			return claims;
		} catch (ExpiredJwtException e) {
			logger.error("Token已过期: {} " + e);
			throw e;
		} catch (UnsupportedJwtException e) {
			logger.error("Token格式错误: {} " + e);
			throw e;
		} catch (MalformedJwtException e) {
			logger.error("Token没有被正确构造: {} " + e);
			throw e;
		} catch (SignatureException e) {
			logger.error("签名失败: {} " + e);
			throw e;
		} catch (IllegalArgumentException e) {
			logger.error("非法参数异常: {} " + e);
			throw e;
		}
	}

	private Date generateExpirationDate() {
		return new Date(System.currentTimeMillis() + expiration * 60 * 1000);
	}

	public Boolean isTokenExpired(String token) throws ClaimJwtException {
		final Date expiration = getExpirationDateFromToken(token);
		return expiration.before(new Date());
	}

	public String generateToken(UserDetails user) {
		String username = user.getUsername();
		List<GrantedAuthority> list = (List<GrantedAuthority>) user.getAuthorities();
		List<String> authorities = new ArrayList<>();
		for (GrantedAuthority g : list) {
			authorities.add(g.getAuthority());
		}
		Map<String, Object> claims = new HashMap<>();
		claims.put(CLAIM_KEY_USERNAME, username);
		claims.put(CLAIM_KEY_AUTH, authorities);
		claims.put(CLAIM_KEY_CREATED, new Date());
		return generateToken(claims);
	}

	private String generateToken(Map<String, Object> claims) {
		return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();
	}

	public String refreshToken(String token) throws ClaimJwtException {
		String refreshedToken = null;
		Claims claims = getClaimsFromToken(token);
		if (null != claims)
			claims.put(CLAIM_KEY_CREATED, new Date());
		refreshedToken = generateToken(claims);
		return refreshedToken;
	}

	public String disabledToken(String token) throws ClaimJwtException {
		String refreshedToken = null;
		Claims claims = getClaimsFromToken(token);
		if (null != claims)
			claims.put(CLAIM_KEY_CREATED, new Date(System.currentTimeMillis() + expiration * 1000));
		refreshedToken = generateToken(claims);
		return refreshedToken;
	}

	public String wrapToken(String token) {
		return TOKEN_PREFIX + token;
	}

	public String parseToken(String token) {
		String t = null;
		if (StrUtil.isNotEmpty(token)) {
			if (token.startsWith(TOKEN_PREFIX)) {
				t = token.substring(TOKEN_PREFIX.length());
			}
		}
		return t;
	}

	public String parseToken(HttpServletRequest request) {
		String token = null;
		String reqToken = request.getHeader(getHeader());
		if (reqToken != null && reqToken.startsWith(JWTTokenConfig.TOKEN_PREFIX)) {
			token = parseToken(reqToken);
		}
		return token;
	}
}
